#ifndef AVALON_WEBSERVICENODE_HPP
#define AVALON_WEBSERVICENODE_HPP
#include "Avalon/Serialization/DataShuttleFactory.hpp"
#include "Avalon/Serialization/Deserializer.hpp"
#include "Avalon/Serialization/Serializer.hpp"
#include "Avalon/SignalHandling/TaskSignalHandler.hpp"
#include "Avalon/WebServices/HttpServerRequest.hpp"
#include "Avalon/WebServices/HttpServerResponse.hpp"
#include "Avalon/WebServices/HttpSessionHandler.hpp"
#include "Avalon/WebServices/HttpStatusCode.hpp"
#include "Avalon/WebServices/WebServiceAdapter.hpp"

namespace Avalon {
namespace WebServices {

  /*! \class WebServiceNode
   *  \brief Handles Avalon service messaging via an HTTP/web interface. 
   *  \author Kamal Mansouri <kranar@hotmail.com>
   */
  class WebServiceNode {
    public:

      //! Constructs a WebServiceNode.
      /*!
        \param server The HTTP server emitting the requests.
        \param sessionFactory Builds new sessions.
        \param dataShuttleFactory The serialization to use for service
                                  messages.
      */
      WebServiceNode(HttpServer* server, HttpSessionFactory* sessionFactory,
        Serialization::DataShuttleFactory* dataShuttleFactory);

      ~WebServiceNode();

      //! Returns the HttpSessionHandler used.
      HttpSessionHandler& GetSessionHandler();

      //! Connects to the invalid request signal.
      /*!
        \param slot The slot to connect.
        \return The <i>slot</i>'s connection to the signal.
      */
      boost::signals2::connection ConnectInvalidRequestSignal(
        const HttpSessionHandler::HttpRequestSignal::slot_type& slot);

      //! Connects a slot to a request signal.
      /*!
        \param predicate The conditions the request must match in order for the
                         signal to be emitted.
        \param slot The slot to connect.
        \return A connection to the request signal.
      */
      template<typename T>
      boost::signals2::connection ConnectRequestSignal(
          const HttpServer::HttpRequestPredicate& predicate, const
          typename WebServiceAdapter<T>::Request::Signal::slot_type& slot) {
        typedef WebServiceAdapter<T> WebServiceType;
        boost::lock_guard<boost::mutex> lock(m_mutex);
        std::map<const std::type_info*,
          boost::signals2::signal_base*>::iterator signalIterator =
          m_signals.find(&typeid(WebServiceType));
        if(signalIterator == m_signals.end()) {
          signalIterator = m_signals.insert(make_pair(&typeid(WebServiceType),
            new typename WebServiceType::Request::Signal())).first;
        }
        m_requestConnections.AddConnection(m_server->ConnectRequestSignal(
          predicate, m_signalHandler.GetSlot<HttpServer::HttpRequestSignal>(
          m_sessionHandler.GetSlot(boost::bind(
          &WebServiceNode::OnRequest<WebServiceType>, this, _1, _2, _3)))));
        return static_cast<typename WebServiceType::Request::Signal*>(
          signalIterator->second)->connect(slot);
      }

    private:
     struct SessionEntry {
        HttpSession* m_session;
        boost::scoped_ptr<Serialization::Serializer> m_serializer;
        boost::scoped_ptr<Serialization::Deserializer> m_deserializer;

        SessionEntry(HttpSession* session,
            Serialization::Serializer* serializer,
            Serialization::Deserializer* deserializer)
            : m_session(session),
              m_serializer(serializer),
              m_deserializer(deserializer) {}
      };
      boost::mutex m_mutex;
      HttpServer* m_server;
      HttpSessionHandler m_sessionHandler;
      boost::scoped_ptr<Serialization::DataShuttleFactory>
        m_dataShuttleFactory;
      std::map<HttpSession*, SessionEntry*> m_sessions;
      std::map<const std::type_info*, boost::signals2::signal_base*> m_signals;
      HttpSessionHandler::HttpRequestSignal m_invalidRequestSignal;
      SignalHandling::ConnectionGroup m_requestConnections;
      SignalHandling::TaskSignalHandler m_signalHandler;

      template<typename T>
      void OnRequest(HttpSession* session, HttpServerRequest* request,
          HttpServerResponse* response) {
        boost::lock_guard<boost::mutex> lock(m_mutex);
        SessionEntry* entry;
        std::map<HttpSession*, SessionEntry*>::iterator sessionIterator =
          m_sessions.find(session);
        if(sessionIterator == m_sessions.end()) {
          entry = new SessionEntry(session,
            m_dataShuttleFactory->CreateSerializer(),
            m_dataShuttleFactory->CreateDeserializer());
          entry->m_serializer->DisableVersioning();
          entry->m_deserializer->DisableVersioning();
          sessionIterator = m_sessions.insert(std::make_pair(session,
            entry)).first;
        } else {
          entry = sessionIterator->second;
        }
        typename T::Request message;
        try {
          if(boost::tuples::length<typename T::Parameters>::value != 0) {
            entry->m_deserializer->SetDataSource(request->GetBody().GetData(),
              request->GetBody().GetSize());
            entry->m_deserializer->Shuttle("message", message);
          }
        } catch(std::exception&) {
          if(m_invalidRequestSignal.empty()) {
            response->SetStatus(HttpStatusCode::BAD_REQUEST);
            response->SendResponse();
            return;
          }
          m_invalidRequestSignal(session, request, response);
          return;
        }
        std::map<const std::type_info*,
          boost::signals2::signal_base*>::iterator signalIterator =
          m_signals.find(&typeid(T));
        if(signalIterator == m_signals.end()) {
          return;
        }
        message.EmitSignal(signalIterator->second, session,
          entry->m_serializer.get(), request, response);
      }
  };

  /*! \class WebServiceRequestToken
   *  \brief Encapsulates a web service request.
   *  \author Kamal Mansouri <kranar@hotmail.com>
   */
  template<typename Service>
  class WebServiceRequestToken {
    public:

      //! Constructs a WebServiceRequestToken.
      /*!
        \param session The HttpSession the request was sent through.
        \param shuttle The Serializer to send to data through.
        \param request The full HTTP request sent.
        \param response The HTTP response.
      */
      WebServiceRequestToken(HttpSession* session,
          Serialization::Serializer* shuttle, HttpServerRequest* request,
          HttpServerResponse* response)
          : m_session(session),
            m_shuttle(shuttle),
            m_request(request),
            m_response(response) {}

      ~WebServiceRequestToken() {}

      //! Returns the HttpSession that sent the request.
      HttpSession* GetSession() const {
        return m_session;
      }

      //! Sends a response to this request.
      /*!
        \param response The response to send to this request.
      */
      void SendResponse(HttpStatusCode statusCode,
          const typename Service::ReturnType& response) const {
        m_response->SetStatus(statusCode);
        m_shuttle->Shuttle("response", response);
        m_response->SetBody(m_shuttle->GetSerializedData());
        m_response->SendResponse();
      }

    private:
      HttpSession* m_session;
      Serialization::Serializer* m_shuttle;
      HttpServerRequest* m_request;
      HttpServerResponse* m_response;
  };

  //! Sets the slot for a web service request.
  /*!
    \param webServiceNode The WebServiceNode emitting the signal.
    \param signalHandler The signal handler receiving the message.
    \param predicate The predicate that the request must satisfy.
    \param slot The slot to connect to the RequestMessage signal.
  */
  template<typename T, typename S>
  void SetHttpRequestHandler(WebServiceNode& webServiceNode, S& signalHandler,
      const HttpServer::HttpRequestPredicate& predicate,
      const typename WebServiceAdapter<T>::Request::Signal::slot_type& slot) {
    const typename WebServiceAdapter<T>::Request::Signal::slot_type& webSlot =
      signalHandler.template GetSlot<
      typename WebServiceAdapter<T>::Request::Signal>(slot);
    webServiceNode.ConnectRequestSignal<T>(predicate, webSlot);
  }
}
}

#endif // AVALON_WEBSERVICENODE_HPP
